/******************************************************************************
*
*
* Notepad2
*
* Print.cpp
*   Scintilla Printing Functionality
*   Mostly taken from SciTE, (c) Neil Hodgson, http://www.scintilla.org
*
* See Readme.txt for more information about this source code.
* Please send me your comments to this work.
*
* See License.txt for details about distribution and modification.
*
*                                              (c) Florian Balmer 1996-2011
*                                                  florian.balmer@gmail.com
*                                               http://www.flos-freeware.ch
*
*
******************************************************************************/
#define _WIN32_WINNT 0x501
#include <windows.h>
#include <commctrl.h>
#include <shlwapi.h>
#include <commdlg.h>
#include <string.h>
#include <vector>
#include "platform.h"
#include "scintilla.h"
#include "scilexer.h"
extern "C" {
#include "dialogs.h"
#include "helpers.h"
}
#include "resource.h"


extern "C" HINSTANCE g_hInstance;


// Global settings...
extern "C" int iPrintHeader;
extern "C" int iPrintFooter;
extern "C" int iPrintColor;
extern "C" int iPrintZoom;
extern "C" RECT pagesetupMargin;


// Stored objects...
HGLOBAL hDevMode = NULL;
HGLOBAL hDevNames = NULL;


//=============================================================================
//
//  EditPrint() - Code from SciTE
//
extern "C" HWND hwndStatus;

void StatusUpdatePrintPage(int iPageNum)
{
  WCHAR tch[32];

  FormatString(tch, COUNTOF(tch), IDS_PRINTFILE, iPageNum);

  StatusSetText(hwndStatus, 255, tch);
  StatusSetSimple(hwndStatus, TRUE);

  InvalidateRect(hwndStatus, NULL, TRUE);
  UpdateWindow(hwndStatus);
}

extern "C" BOOL EditPrint(HWND hwnd, LPCWSTR pszDocTitle, LPCWSTR pszPageFormat)
{

  // Don't print empty documents
  if (SendMessage(hwnd, SCI_GETLENGTH, 0, 0) == 0) {
    MsgBox(MBWARN, IDS_PRINT_EMPTY);
    return TRUE;
  }

  int startPos;
  int endPos;

  HDC hdc;

  RECT rectMargins;
  RECT rectPhysMargins;
  RECT rectSetup;
  POINT ptPage;
  POINT ptDpi;

  TEXTMETRIC tm;

  int headerLineHeight;
  HFONT fontHeader;

  int footerLineHeight;
  HFONT fontFooter;

  WCHAR dateString[256];

  DOCINFO di ={sizeof(DOCINFO), 0, 0, 0, 0};

  LONG lengthDoc;
  LONG lengthDocMax;
  LONG lengthPrinted;

  struct RangeToFormat frPrint;

  int pageNum;
  BOOL printPage;

  WCHAR pageString[32];

  HPEN pen;
  HPEN penOld;

  PRINTDLG pdlg ={sizeof(PRINTDLG), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
  pdlg.hwndOwner = GetParent(hwnd);
  pdlg.hInstance = g_hInstance;
  pdlg.Flags = PD_USEDEVMODECOPIES | PD_ALLPAGES | PD_RETURNDC;
  pdlg.nFromPage = 1;
  pdlg.nToPage = 1;
  pdlg.nMinPage = 1;
  pdlg.nMaxPage = 0xffffU;
  pdlg.nCopies = 1;
  pdlg.hDC = 0;
  pdlg.hDevMode = hDevMode;
  pdlg.hDevNames = hDevNames;

  startPos = SendMessage(hwnd, SCI_GETSELECTIONSTART, 0, 0);;
  endPos = SendMessage(hwnd, SCI_GETSELECTIONEND, 0, 0);

  if (startPos == endPos) {
    pdlg.Flags |= PD_NOSELECTION;
  }
  else {
    pdlg.Flags |= PD_SELECTION;
  }
  if (0) {
    // Don't display dialog box, just use the default printer and options
    pdlg.Flags |= PD_RETURNDEFAULT;
  }
  if (!PrintDlg(&pdlg)) {
    return TRUE; // False means error...
  }

  hDevMode = pdlg.hDevMode;
  hDevNames = pdlg.hDevNames;

  hdc = pdlg.hDC;

  // Get printer resolution
  ptDpi.x = GetDeviceCaps(hdc, LOGPIXELSX);    // dpi in X direction
  ptDpi.y = GetDeviceCaps(hdc, LOGPIXELSY);    // dpi in Y direction

  // Start by getting the physical page size (in device units).
  ptPage.x = GetDeviceCaps(hdc, PHYSICALWIDTH);   // device units
  ptPage.y = GetDeviceCaps(hdc, PHYSICALHEIGHT);  // device units

  // Get the dimensions of the unprintable
  // part of the page (in device units).
  rectPhysMargins.left = GetDeviceCaps(hdc, PHYSICALOFFSETX);
  rectPhysMargins.top = GetDeviceCaps(hdc, PHYSICALOFFSETY);

  // To get the right and lower unprintable area,
  // we take the entire width and height of the paper and
  // subtract everything else.
  rectPhysMargins.right = ptPage.x            // total paper width
    - GetDeviceCaps(hdc, HORZRES) // printable width
    - rectPhysMargins.left;        // left unprintable margin

  rectPhysMargins.bottom = ptPage.y            // total paper height
    - GetDeviceCaps(hdc, VERTRES)  // printable height
    - rectPhysMargins.top;        // right unprintable margin

// At this point, rectPhysMargins contains the widths of the
// unprintable regions on all four sides of the page in device units.

// Take in account the page setup given by the user (if one value is not null)
  if (pagesetupMargin.left != 0 || pagesetupMargin.right != 0 ||
      pagesetupMargin.top != 0 || pagesetupMargin.bottom != 0) {
// Convert the hundredths of millimeters (HiMetric) or
// thousandths of inches (HiEnglish) margin values
// from the Page Setup dialog to device units.
// (There are 2540 hundredths of a mm in an inch.)

    WCHAR localeInfo[3];
    GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_IMEASURE, localeInfo, 3);

    if (localeInfo[0] == L'0') {  // Metric system. L'1' is US System
      rectSetup.left = MulDiv(pagesetupMargin.left, ptDpi.x, 2540);
      rectSetup.top = MulDiv(pagesetupMargin.top, ptDpi.y, 2540);
      rectSetup.right  = MulDiv(pagesetupMargin.right, ptDpi.x, 2540);
      rectSetup.bottom  = MulDiv(pagesetupMargin.bottom, ptDpi.y, 2540);
    }
    else {
      rectSetup.left  = MulDiv(pagesetupMargin.left, ptDpi.x, 1000);
      rectSetup.top  = MulDiv(pagesetupMargin.top, ptDpi.y, 1000);
      rectSetup.right  = MulDiv(pagesetupMargin.right, ptDpi.x, 1000);
      rectSetup.bottom  = MulDiv(pagesetupMargin.bottom, ptDpi.y, 1000);
    }

    // Dont reduce margins below the minimum printable area
    rectMargins.left  = max(rectPhysMargins.left, rectSetup.left);
    rectMargins.top  = max(rectPhysMargins.top, rectSetup.top);
    rectMargins.right  = max(rectPhysMargins.right, rectSetup.right);
    rectMargins.bottom  = max(rectPhysMargins.bottom, rectSetup.bottom);
  }
  else {
    rectMargins.left  = rectPhysMargins.left;
    rectMargins.top  = rectPhysMargins.top;
    rectMargins.right  = rectPhysMargins.right;
    rectMargins.bottom  = rectPhysMargins.bottom;
  }

  // rectMargins now contains the values used to shrink the printable
  // area of the page.

  // Convert device coordinates into logical coordinates
  DPtoLP(hdc, (LPPOINT)&rectMargins, 2);
  DPtoLP(hdc, (LPPOINT)&rectPhysMargins, 2);

  // Convert page size to logical units and we're done!
  DPtoLP(hdc, (LPPOINT)&ptPage, 1);

  headerLineHeight = MulDiv(10, ptDpi.y, 72);
  fontHeader = CreateFont(headerLineHeight,
                          0, 0, 0,
                          FW_BOLD,
                          0,
                          0,
                          0, 0, 0,
                          0, 0, 0,
                          L"Arial");
  SelectObject(hdc, fontHeader);
  GetTextMetrics(hdc, &tm);
  headerLineHeight = tm.tmHeight + tm.tmExternalLeading;

  if (iPrintHeader == 3)
    headerLineHeight = 0;

  footerLineHeight = MulDiv(10, ptDpi.y, 72);
  fontFooter = CreateFont(footerLineHeight,
                          0, 0, 0,
                          FW_NORMAL,
                          0,
                          0,
                          0, 0, 0,
                          0, 0, 0,
                          L"Arial");
  SelectObject(hdc, fontFooter);
  GetTextMetrics(hdc, &tm);
  footerLineHeight = tm.tmHeight + tm.tmExternalLeading;

  if (iPrintFooter == 1)
    footerLineHeight = 0;

  di.lpszDocName = pszDocTitle;
  di.lpszOutput = 0;
  di.lpszDatatype = 0;
  di.fwType = 0;
  if (StartDoc(hdc, &di) < 0) {
    DeleteDC(hdc);
    if (fontHeader)
      DeleteObject(fontHeader);
    if (fontFooter)
      DeleteObject(fontFooter);
    return FALSE;
  }

  // Get current date...
  SYSTEMTIME st;
  GetLocalTime(&st);
  GetDateFormat(LOCALE_USER_DEFAULT, DATE_SHORTDATE, &st, NULL, dateString, 256);

  // Get current time...
  if (iPrintHeader == 0) {
    WCHAR timeString[128];
    GetTimeFormat(LOCALE_USER_DEFAULT, TIME_NOSECONDS, &st, NULL, timeString, 128);
    lstrcat(dateString, L" ");
    lstrcat(dateString, timeString);
  }

  // Set print color mode
  int printColorModes[5] ={
    SC_PRINT_NORMAL,
    SC_PRINT_INVERTLIGHT,
    SC_PRINT_BLACKONWHITE,
    SC_PRINT_COLOURONWHITE,
    SC_PRINT_COLOURONWHITEDEFAULTBG};
  SendMessage(hwnd, SCI_SETPRINTCOLOURMODE, printColorModes[iPrintColor], 0);

  // Set print zoom...
  SendMessage(hwnd, SCI_SETPRINTMAGNIFICATION, (WPARAM)iPrintZoom, 0);

  lengthDoc = SendMessage(hwnd, SCI_GETLENGTH, 0, 0);
  lengthDocMax = lengthDoc;
  lengthPrinted = 0;

  // Requested to print selection
  if (pdlg.Flags & PD_SELECTION) {
    if (startPos > endPos) {
      lengthPrinted = endPos;
      lengthDoc = startPos;
    }
    else {
      lengthPrinted = startPos;
      lengthDoc = endPos;
    }

    if (lengthPrinted < 0)
      lengthPrinted = 0;
    if (lengthDoc > lengthDocMax)
      lengthDoc = lengthDocMax;
  }

  // We must substract the physical margins from the printable area
  frPrint.hdc = hdc;
  frPrint.hdcTarget = hdc;
  frPrint.rc.left = rectMargins.left - rectPhysMargins.left;
  frPrint.rc.top = rectMargins.top - rectPhysMargins.top;
  frPrint.rc.right = ptPage.x - rectMargins.right - rectPhysMargins.left;
  frPrint.rc.bottom = ptPage.y - rectMargins.bottom - rectPhysMargins.top;
  frPrint.rcPage.left = 0;
  frPrint.rcPage.top = 0;
  frPrint.rcPage.right = ptPage.x - rectPhysMargins.left - rectPhysMargins.right - 1;
  frPrint.rcPage.bottom = ptPage.y - rectPhysMargins.top - rectPhysMargins.bottom - 1;
  frPrint.rc.top += headerLineHeight + headerLineHeight / 2;
  frPrint.rc.bottom -= footerLineHeight + footerLineHeight / 2;
  // Print each page
  pageNum = 1;

  while (lengthPrinted < lengthDoc) {
    printPage = (!(pdlg.Flags & PD_PAGENUMS) ||
                 (pageNum >= pdlg.nFromPage) && (pageNum <= pdlg.nToPage));

    wsprintf(pageString, pszPageFormat, pageNum);

    if (printPage) {

      // Show wait cursor...
      BeginWaitCursor();

      // Display current page number in Statusbar
      StatusUpdatePrintPage(pageNum);

      StartPage(hdc);

      SetTextColor(hdc, RGB(0, 0, 0));
      SetBkColor(hdc, RGB(255, 255, 255));
      SelectObject(hdc, fontHeader);
      UINT ta = SetTextAlign(hdc, TA_BOTTOM);
      RECT rcw ={frPrint.rc.left, frPrint.rc.top - headerLineHeight - headerLineHeight / 2,
                  frPrint.rc.right, frPrint.rc.top - headerLineHeight / 2};
      rcw.bottom = rcw.top + headerLineHeight;

      if (iPrintHeader < 3) {
        ExtTextOut(hdc, frPrint.rc.left + 5, frPrint.rc.top - headerLineHeight / 2,
                   0, &rcw, pszDocTitle,
                   lstrlen(pszDocTitle), NULL);
      }

      // Print date in header
      if (iPrintHeader == 0 || iPrintHeader == 1) {
        SIZE sizeInfo;
        SelectObject(hdc, fontFooter);
        GetTextExtentPoint32(hdc, dateString, lstrlen(dateString), &sizeInfo);
        ExtTextOut(hdc, frPrint.rc.right - 5 - sizeInfo.cx, frPrint.rc.top - headerLineHeight / 2,
                   0, &rcw, dateString,
                   lstrlen(dateString), NULL);
      }

      if (iPrintHeader < 3) {
        SetTextAlign(hdc, ta);
        pen = CreatePen(0, 1, RGB(0, 0, 0));
        penOld = (HPEN)SelectObject(hdc, pen);
        MoveToEx(hdc, frPrint.rc.left, frPrint.rc.top - headerLineHeight / 4, NULL);
        LineTo(hdc, frPrint.rc.right, frPrint.rc.top - headerLineHeight / 4);
        SelectObject(hdc, penOld);
        DeleteObject(pen);
      }
    }

    frPrint.chrg.cpMin = lengthPrinted;
    frPrint.chrg.cpMax = lengthDoc;

    lengthPrinted = SendMessage(hwnd, SCI_FORMATRANGE, printPage, (LPARAM)&frPrint);

    if (printPage) {
      SetTextColor(hdc, RGB(0, 0, 0));
      SetBkColor(hdc, RGB(255, 255, 255));
      SelectObject(hdc, fontFooter);
      UINT ta = SetTextAlign(hdc, TA_TOP);
      RECT rcw ={frPrint.rc.left, frPrint.rc.bottom + footerLineHeight / 2,
                  frPrint.rc.right, frPrint.rc.bottom + footerLineHeight + footerLineHeight / 2};

      if (iPrintFooter == 0) {
        SIZE sizeFooter;
        GetTextExtentPoint32(hdc, pageString, lstrlen(pageString), &sizeFooter);
        ExtTextOut(hdc, frPrint.rc.right - 5 - sizeFooter.cx, frPrint.rc.bottom + footerLineHeight / 2,
                   0, &rcw, pageString,
                   lstrlen(pageString), NULL);

        SetTextAlign(hdc, ta);
        pen = ::CreatePen(0, 1, RGB(0, 0, 0));
        penOld = (HPEN)SelectObject(hdc, pen);
        SetBkColor(hdc, RGB(0, 0, 0));
        MoveToEx(hdc, frPrint.rc.left, frPrint.rc.bottom + footerLineHeight / 4, NULL);
        LineTo(hdc, frPrint.rc.right, frPrint.rc.bottom + footerLineHeight / 4);
        SelectObject(hdc, penOld);
        DeleteObject(pen);
      }

      EndPage(hdc);
    }
    pageNum++;

    if ((pdlg.Flags & PD_PAGENUMS) && (pageNum > pdlg.nToPage))
      break;
  }

  SendMessage(hwnd, SCI_FORMATRANGE, FALSE, 0);

  EndDoc(hdc);
  DeleteDC(hdc);
  if (fontHeader)
    DeleteObject(fontHeader);
  if (fontFooter)
    DeleteObject(fontFooter);

  // Reset Statusbar to default mode
  StatusSetSimple(hwndStatus, FALSE);

  // Remove wait cursor...
  EndWaitCursor();

  return TRUE;
}


//=============================================================================
//
//  EditPrintSetup() - Code from SciTE
//
//  Custom controls: 30 Zoom
//                   31 Spin
//                   32 Header
//                   33 Footer
//                   34 Colors
//
extern "C" UINT_PTR CALLBACK PageSetupHook(HWND hwnd, UINT uiMsg, WPARAM wParam, LPARAM lParam)
{
  switch (uiMsg) {
    case WM_INITDIALOG:
      {
        WCHAR tch[512];
        WCHAR *p1, *p2;

        SendDlgItemMessage(hwnd, 30, EM_LIMITTEXT, 32, 0);

        SendDlgItemMessage(hwnd, 31, UDM_SETRANGE, 0, MAKELONG((short)20, (short)-10));
        SendDlgItemMessage(hwnd, 31, UDM_SETPOS, 0, MAKELONG((short)iPrintZoom, 0));

        // Set header options
        GetString(IDS_PRINT_HEADER, tch, COUNTOF(tch));
        lstrcat(tch, L"|");
        p1 = tch;
        while (p2 = StrChr(p1, L'|')) {
          *p2++ = L'\0';
          if (*p1)
            SendDlgItemMessage(hwnd, 32, CB_ADDSTRING, 0, (LPARAM)p1);
          p1 = p2;
        }
        SendDlgItemMessage(hwnd, 32, CB_SETCURSEL, (WPARAM)iPrintHeader, 0);

        // Set footer options
        GetString(IDS_PRINT_FOOTER, tch, COUNTOF(tch));
        lstrcat(tch, L"|");
        p1 = tch;
        while (p2 = StrChr(p1, L'|')) {
          *p2++ = L'\0';
          if (*p1)
            SendDlgItemMessage(hwnd, 33, CB_ADDSTRING, 0, (LPARAM)p1);
          p1 = p2;
        }
        SendDlgItemMessage(hwnd, 33, CB_SETCURSEL, (WPARAM)iPrintFooter, 0);

        // Set color options
        GetString(IDS_PRINT_COLOR, tch, COUNTOF(tch));
        lstrcat(tch, L"|");
        p1 = tch;
        while (p2 = StrChr(p1, L'|')) {
          *p2++ = L'\0';
          if (*p1)
            SendDlgItemMessage(hwnd, 34, CB_ADDSTRING, 0, (LPARAM)p1);
          p1 = p2;
        }
        SendDlgItemMessage(hwnd, 34, CB_SETCURSEL, (WPARAM)iPrintColor, 0);

        // Make combos handier
        SendDlgItemMessage(hwnd, 32, CB_SETEXTENDEDUI, TRUE, 0);
        SendDlgItemMessage(hwnd, 33, CB_SETEXTENDEDUI, TRUE, 0);
        SendDlgItemMessage(hwnd, 34, CB_SETEXTENDEDUI, TRUE, 0);
        SendDlgItemMessage(hwnd, 1137, CB_SETEXTENDEDUI, TRUE, 0);
        SendDlgItemMessage(hwnd, 1138, CB_SETEXTENDEDUI, TRUE, 0);
      }
      break;

    case WM_COMMAND:
      if (LOWORD(wParam) == IDOK) {
        LONG lPos = SendDlgItemMessage(hwnd, 31, UDM_GETPOS, 0, 0);
        if (HIWORD(lPos) == 0)
          iPrintZoom = (int)(short)LOWORD(lPos);
        else
          iPrintZoom = 0;

        iPrintHeader = SendDlgItemMessage(hwnd, 32, CB_GETCURSEL, 0, 0);
        iPrintFooter = SendDlgItemMessage(hwnd, 33, CB_GETCURSEL, 0, 0);
        iPrintColor  = SendDlgItemMessage(hwnd, 34, CB_GETCURSEL, 0, 0);
      }
      break;

    default:
      break;
  }
  return(0);
}


extern "C" void EditPrintSetup(HWND hwnd)
{
  DLGTEMPLATE* pDlgTemplate =
    LoadThemedDialogTemplate(MAKEINTRESOURCE(IDD_PAGESETUP), g_hInstance);

  PAGESETUPDLG pdlg;
  ZeroMemory(&pdlg, sizeof(PAGESETUPDLG));
  pdlg.lStructSize = sizeof(PAGESETUPDLG);
  pdlg.Flags = PSD_ENABLEPAGESETUPHOOK | PSD_ENABLEPAGESETUPTEMPLATEHANDLE;
  pdlg.lpfnPageSetupHook = PageSetupHook;
  pdlg.hPageSetupTemplate = pDlgTemplate;
  pdlg.hwndOwner = GetParent(hwnd);
  pdlg.hInstance = g_hInstance;

  if (pagesetupMargin.left != 0 || pagesetupMargin.right != 0 ||
      pagesetupMargin.top != 0 || pagesetupMargin.bottom != 0) {
    pdlg.Flags |= PSD_MARGINS;

    pdlg.rtMargin.left = pagesetupMargin.left;
    pdlg.rtMargin.top = pagesetupMargin.top;
    pdlg.rtMargin.right = pagesetupMargin.right;
    pdlg.rtMargin.bottom = pagesetupMargin.bottom;
  }

  pdlg.hDevMode = hDevMode;
  pdlg.hDevNames = hDevNames;

  if (PageSetupDlg(&pdlg)) {

    pagesetupMargin.left = pdlg.rtMargin.left;
    pagesetupMargin.top = pdlg.rtMargin.top;
    pagesetupMargin.right = pdlg.rtMargin.right;
    pagesetupMargin.bottom = pdlg.rtMargin.bottom;

    hDevMode = pdlg.hDevMode;
    hDevNames = pdlg.hDevNames;
  }

  LocalFree(pDlgTemplate);
}


//=============================================================================
//
//  EditPrintInit() - Setup default page margin if no values from registry
//
extern "C" void EditPrintInit()
{
  if (pagesetupMargin.left == -1 || pagesetupMargin.top == -1 ||
      pagesetupMargin.right == -1 || pagesetupMargin.bottom == -1) {
    WCHAR localeInfo[3];
    GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_IMEASURE, localeInfo, 3);

    if (localeInfo[0] == L'0') {  // Metric system. L'1' is US System
      pagesetupMargin.left = 2000;
      pagesetupMargin.top = 2000;
      pagesetupMargin.right = 2000;
      pagesetupMargin.bottom = 2000;
    }

    else {
      pagesetupMargin.left = 1000;
      pagesetupMargin.top = 1000;
      pagesetupMargin.right = 1000;
      pagesetupMargin.bottom = 1000;
    }
  }
}
